﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Legend.Collections;
using FakeItEasy; using FakeItEasy.ExtensionSyntax.Full;
using System.Diagnostics;

namespace Legend.Core.Tests.Collections.ReadOnlyDictionaryTests
{
    public class ReadOnlyDictionaryTestCase
    {
        protected IDictionary<string, string> wrappedDictionary;
        protected ReadOnlyDictionary<string, string> dictionary;

        protected IDictionary<string, string> TestedDictionary
        {
            get
            {
                return this.dictionary;
            }
        }

        [SetUp]
        public void SetUp()
        {
            this.wrappedDictionary = this.CreateWrappedDictionary();
            this.dictionary = new ReadOnlyDictionary<string, string>(this.wrappedDictionary);
        }

        protected virtual IDictionary<string, string> CreateWrappedDictionary()
        {
            return A.Fake<IDictionary<string, string>>();
        }
    }

    [TestFixture]
    public class Constructor
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_dictionary_is_null()
        {
            var d = new ReadOnlyDictionary<string, string>(null);
        }
    }

    [TestFixture]
    public class DictionaryAdd
        : ReadOnlyDictionaryTestCase 
    {
        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_called()
        {
            this.TestedDictionary.Add("foo", "bar");
        }
    }

    [TestFixture]
    public class Keys
        : ReadOnlyDictionaryTestCase
    {
        [Test]
        public void should_return_keys_collection_from_wrapped_dictionary()
        {
            var returned = dictionary.Keys;
            Assert.AreEqual("foo", returned.Single());
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>(){
                { "foo", "bar" }
            };
        }
    }

    [TestFixture]
    public class Values
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void should_return_values_collection_from_wrapped_dictionary()
        {
            var returned = dictionary.Values;
            Assert.AreEqual("bar", returned.Single());
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>(){
                { "foo", "bar" }
            };
        }
    }

    [TestFixture]
    public class ContainsKey
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void should_return_true_when_inner_dictionary_contains_key()
        {
            A.CallTo(() => wrappedDictionary.ContainsKey("foo")).Returns(true);

            Assert.IsTrue(this.dictionary.ContainsKey("foo"));
        }

        [Test]
        public void should_return_false_when_inner_dictionary_does_not_contain_key()
        {
            A.CallTo(() => wrappedDictionary.ContainsKey("foo")).Returns(false);
            Assert.IsFalse(this.dictionary.ContainsKey("foo"));
        }
    }

    [TestFixture]
    public class Remove
        : ReadOnlyDictionaryTestCase
    {
        [Test]
        public void should_call_remove_on_wrapped_dictionary()
        {
            this.TestedDictionary.Remove("foo");

            A.CallTo(() => this.wrappedDictionary.Remove("foo")).MustHaveHappened();
        }

        [Test]
        public void should_return_true_when_value_was_removed_in_wrapped_dictionary()
        {
            A.CallTo(() => wrappedDictionary.Remove("foo")).Returns(true);

            Assert.IsTrue(this.TestedDictionary.Remove("foo"));
        }

        [Test]
        public void should_return_false_when_value_was_not_removed_in_wrapped_dictionary()
        {
            A.CallTo(() => wrappedDictionary.Remove("foo")).Returns(false);

            Assert.IsFalse(this.TestedDictionary.Remove("foo"));
        }
    }

    [TestFixture]
    public class TryGetValue
        : ReadOnlyDictionaryTestCase 
    {
        string s;

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_key_is_null()
        {
            this.dictionary.TryGetValue(null, out s);
        }

        [Test]
        public void should_return_true_when_value_exists()
        {
            Assert.IsTrue(this.dictionary.TryGetValue("foo", out s));
        }

        [Test]
        public void should_return_false_when_value_does_not_exist()
        {
            Assert.IsFalse(this.dictionary.TryGetValue("bar", out s));
        }

        [Test]
        public void should_set_value_to_output_variable_when_exists()
        {
            this.dictionary.TryGetValue("foo", out this.s);
            Assert.AreEqual("bar", this.s);
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>() {
                {"foo", "bar"}
            };
        }
    }

    [TestFixture]
    public class read_only_indexer
        : ReadOnlyDictionaryTestCase 
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_key_is_null()
        {
            var s = this.dictionary[null];
        }

        [Test]
        public void should_return_value_returned_from_wrapped_dictionary()
        {
            this.wrappedDictionary.CallsTo(x => x["foo"]).Returns("bar");
            var returned = this.dictionary["foo"];
            Assert.AreEqual("bar", returned);
        }
    }

    [TestFixture]
    public class dictionary_indexer
        : ReadOnlyDictionaryTestCase
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_getting_with_null_key()
        {
            var s = this.dictionary[null];
        }

        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_setting_with_null_key()
        {
            this.TestedDictionary[null] = "bar";
        }

        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_setting_with_valid_key()
        {
            this.TestedDictionary["foo"] = "bar";
        }

        [Test]
        public void should_return_value_from_wrapped_dictionary_when_getting()
        {
            this.wrappedDictionary.CallsTo(x => x["foo"]).Returns("bar");
            var returned = this.dictionary["foo"];

            Assert.AreEqual("bar", returned);
        }
    }

    [TestFixture]
    public class collection_add
        : ReadOnlyDictionaryTestCase
    {
        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_called()
        {
            this.TestedDictionary.Add(new KeyValuePair<string, string>("foo", "bar"));
        }
    }

    [TestFixture]
    public class Contains
        : ReadOnlyDictionaryTestCase 
    {
        KeyValuePair<string, string> pair = new KeyValuePair<string, string>("foo", "bar");

        [Test]
        public void calls_contains_on_wrapped_dictionary()
        {
            dictionary.Contains(pair);
            A.CallTo(() => wrappedDictionary.Contains(pair)).MustHaveHappened();
        }

        [Test]
        public void returns_false_when_wrapped_dictionary_returns_false()
        {
            wrappedDictionary.CallsTo(x => x.Contains(pair)).Returns(false);
            var returned = dictionary.Contains(pair);
            Assert.IsFalse(returned);
        }

        [Test]
        public void returns_true_when_wrapped_dictionary_returns_true()
        {
            wrappedDictionary.CallsTo(x => x.Contains(pair)).Returns(true);
            var returned = dictionary.Contains(pair);
            Assert.IsTrue(returned);
        }
    }

    [TestFixture]
    public class clear
        : ReadOnlyDictionaryTestCase
    {
        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_called()
        {
            TestedDictionary.Clear();
        }
    }

    [TestFixture]
    public class CopyTo
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void calls_copy_to_on_wrapped_dictionary()
        {
            var array = new KeyValuePair<string, string>[10];
            dictionary.CopyTo(array, 0);
            A.CallTo(() => wrappedDictionary.CopyTo(array, 0)).MustHaveHappened();
        }
    }

    [TestFixture]
    public class Count
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void returns_count_of_wrapped_dictionary()
        {
            wrappedDictionary.CallsTo(x => x.Count).Returns(1);
            Assert.AreEqual(1, dictionary.Count);

            A.CallTo(() => wrappedDictionary.Count).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

    [TestFixture]
    public class IsReadOnly
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void returns_true()
        {
            Assert.IsTrue(TestedDictionary.IsReadOnly);
        }
    }

    [TestFixture]
    public class collection_Remove
        : ReadOnlyDictionaryTestCase 
    {
        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_called()
        {
            TestedDictionary.Remove(new KeyValuePair<string, string>());
        }
    }

    [TestFixture]
    public class GetEnumerator
        : ReadOnlyDictionaryTestCase 
    {
        [Test]
        public void gets_the_enumerator_of_the_wrapped_dictionary()
        {
            var e = new FakeEnumerator();
            wrappedDictionary.CallsTo(x => x.GetEnumerator()).Returns(e);

            var returned = dictionary.GetEnumerator();
            Assert.AreEqual(e, returned);
        }

        [Test]
        public void get_the_enumerator_of_the_wrapped_dictionary_through_non_generic_interface()
        {
            var e = new FakeEnumerator();
            ((System.Collections.IEnumerable)wrappedDictionary).CallsTo(x => x.GetEnumerator()).Returns(e);

            var returned = ((System.Collections.IEnumerable)dictionary).GetEnumerator();

            Assert.AreEqual(e, returned);
        }

        private class FakeEnumerator
            : IEnumerator<KeyValuePair<string, string>>
        {
            

            public KeyValuePair<string, string> Current
            {
                get { throw new NotImplementedException(); }
            }

            

            

            public void Dispose()
            {
                throw new NotImplementedException();
            }

            

            

            object System.Collections.IEnumerator.Current
            {
                get { throw new NotImplementedException(); }
            }

            public bool MoveNext()
            {
                throw new NotImplementedException();
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }

            
        }
    }
}
